package com.bjpowernode.app.service;

import com.bjpowernode.app.dao.ReportDao;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

//缓存在业务层中处理
//1:进入缓存拿出数据
//2:判断是否拿到
//2-1:拿到，直接返回，不需要连接数据库了，换句话说不需要调dao（减轻了数据库压力）
//2-2:没拿到，调dao查结果，然后加入缓存。
@Service
public class LoanServiceImpl implements LoanService {
    @Autowired
    ReportDao reportDao;
    //SpringBoot给了缓存摸版操作
    @Autowired
     RedisTemplate<Object, Object> redisTemplate;



    @Override
    public  double avgLoanRate() {

        //把key进行字符串序列化，让我们可以在redis数据库中看到具体的字符
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //把value进行字符串序列化，让我们可以在redis数据库中看到具体的字符
        redisTemplate.setValueSerializer(new StringRedisSerializer());







        //reportDao.avgLoanRateReport()这个方法意味着去连接数据库，查询数据库，关闭连接
        //因此是直接操作数据库的
        //在没有缓存的情况下直接操作数据库，后果是给数据库带来巨大压力
        //double avgLoanRateReport = reportDao.avgLoanRateReport();

        //缓存穿透:做了缓存依然连接数据库。
        //方案一：双检查+锁(一检防进数据库，二检防同步)
        //缓存与数据库的一致性；数据库更新，缓存没有更新导致数据不一致
        //缓存穿透2：查询不存在（数据库查不到）【布隆过滤器】【搞个好大的map，里面全部存储可以想象的到不存在的id】
        //缓存雪崩（缓存在某一时刻大量key失效，也就是时间到了过期了）：缓存的失效时间一致，在某一刻，所有缓存全部集体失效
        //缓存击穿（缓存在某一时刻某一个key,高并发）：缓存扛不住
        //缓存操作流程
        //1：定义结果类型，这里一定是包装类，因为非包装类会转null失败
        //2：从缓存拿数据
        //Double avgLoanRateReport=缓存拿到的东西,缓存拿不到，返回的是null;
        //a,b,c,d,e,f
        Double avgLoanRateReport = (Double) redisTemplate.opsForValue().get("avgLoanRateReport");
        //如果对象为null意味着缓存无货【注意这里的ObjectUtils.allNotNull的判断是反的】
        //2：【这个判断不加没事，这是优化】,因为不需要都在这里等，先判断缓存是否有数据，如果有，无需同步
        if (!ObjectUtils.allNotNull(avgLoanRateReport)) {
            //a=线程调用栈,b=线程调用栈,c=线程调用栈,d=线程调用栈
            synchronized (this) {
                //当前线程调用栈读取当前线程调用栈中的变量
                //表达A线程中的avgLoanRateReport变量
                avgLoanRateReport = (Double) redisTemplate.opsForValue().get("avgLoanRateReport");
                //1：枷锁后，再次判断，目的是为了绝对一个线程操作数据【这个判断不加是错误】
                //表达A线程中的avgLoanRateReport变量的判断
                if (!ObjectUtils.allNotNull(avgLoanRateReport)) {
                    //avgLoanRateReport变量实际赋值了
                    avgLoanRateReport = reportDao.avgLoanRateReport();
                    System.out.println("进入数据库取数据..............................");
                    //丢到缓存
                    redisTemplate.opsForValue().set("avgLoanRateReport", String.valueOf(avgLoanRateReport), 30, TimeUnit.SECONDS);
                }
            }
        }
        return avgLoanRateReport;
    }
}
